package utils;

import Log.ISysLog;
import Log.TxtLog;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.lang.reflect.*;
import java.util.*;


/**
 * JSON工具类
 * 处理和JSON相关的所有内容
 */
public class JSONUtils {
    protected static ISysLog log = new TxtLog();
    // 字符串null
    private final static String StringNull = "null";

    /**
     * 把对象转换成JSON格式的字符串
     *
     * @param entity 指定对象
     * @return 返回JSON格式的字符串
     */
    public static String entity2JSON(Object entity) {
        // 先处理特殊Java已定义的对象类型
        if (entity == null) return StringNull;

        // String类型
        if (entity instanceof String) {
            return '\"' + escape((String) entity) + '\"';
        }

        // Double类型
        if (entity instanceof Double) {
            Double temporarilyDouble = (Double) entity;
            // 若为无限值或不能转换为数值 直接返回null串
            if (temporarilyDouble.isInfinite() || temporarilyDouble.isNaN()) return StringNull;
            return entity.toString();
        }

        // Float类型
        if (entity instanceof Float) {
            Float temporarilyFloat = (Float) entity;
            // 若为无限值或不能转换为数值 直接返回null串
            if (temporarilyFloat.isInfinite() || temporarilyFloat.isNaN()) return StringNull;
            return entity.toString();
        }

        // Date类型
        if (entity instanceof Date) {
            Date date = (Date) entity;
            long time = date.getTime();
            return '\"' + String.valueOf(time) + '\"';
        }


        // 如果属于数值类型 直接转换toString
        if (entity instanceof Number) return entity.toString();

        // 集合类型 分为Map 和 Collection
        if (entity instanceof Map) return map2JsonString((Map<Object, Object>) entity);

        if (entity instanceof Collection) return collection2JsonString((Collection<Object>) entity);

        // 判断是否为数组
        if (entity.getClass().isArray()) return array2JsonString(entity);


        // 若都不符合 认为当前对象为一个自定实体类
        return entityObject2JsonString(entity);
    }


    /**
     * 把JSON字符串转换成指定类型的对象
     * <p>
     * ? 泛型的通配符，代表的是未知的任意类型，或者说是Object
     *
     * @param json  要转换的数据
     * @param clazz 指定的类型
     * @return 返回Object对象
     */
    public static <T> T JSON2Entity(String json, Class<T> clazz) {
        JSONObject jsonObject = null;
        // 判断字符串是否为json字符串
        try {
            jsonObject = new JSONObject(json);
        } catch (JSONException e) {
            System.out.println("异常1：" + e.getMessage());
        }

        return JSON2Entity(jsonObject, clazz);
    }


    /**
     * 用于递归转换
     * @param jsonObject
     * @param clazz
     * @return
     * @param <T>
     */
    public static <T> T JSON2Entity(JSONObject jsonObject, Class<T> clazz) {
        T o = null;
        try {
            // 获取对象实例
            o = clazz.newInstance();
            // 获取对象属性列表
            List<Field> fields = getOwnAndSuperDeclaredFields(clazz);
            // 遍历属性 基础类型和String直接赋值
            for (Field field : fields) {
                // 判断字段是否为静态 静态直接跳过
                if (Modifier.isStatic(field.getModifiers())) continue;
                // 判断字段是否为null 取值为null或抛出异常都直接跳过
                try {
                    if (jsonObject.isNull(field.getName())) continue;
                } catch (JSONException e) {
                    log.info(e.getMessage());
                    continue;
                }

                // 如果当前字段取不到json对象值直接跳过
                // 设置属性可操作
                field.setAccessible(true);
                // 获取字段类型
                Class<?> typeClazz = field.getType();
                // 判断字段的class对象类型
                // 是否为基础变量
                if (typeClazz.isPrimitive()) {
                    if ("double".equals(typeClazz.getTypeName())) {
                        setProperty(field, o, jsonObject.optDouble(field.getName()));
                    } else if ("int".equals(typeClazz.getTypeName())) {
                        setProperty(field, o, jsonObject.optInt(field.getName()));
                    }
                } else {
                    Object fieldObj;
                    // 判断字段类型为list还是对象 // 通过ArrayList Class对象得到一个实例对象
                    if (typeClazz.isInterface() && typeClazz.getSimpleName().contains("list"))
                        fieldObj = ArrayList.class.newInstance();
                    else {
                        fieldObj = typeClazz.newInstance();
                    }


                    // 判断类型 当字段类型为List 认为是一个数组
                    if (fieldObj instanceof List) {
                        // 得到类型的结构 例：java.util.List<model.Address>
                        Type genericType = field.getGenericType();
                        // 强转为ParameterizedType类型 调用子接口方法
                        ParameterizedType parameterizedType = (ParameterizedType) genericType;
                        // 获取到list集合的泛型实际类型 获取参数列表的第一个泛型类型 list集合只有一个泛型只需要获取一次
                        Class<? extends Type> clz = parameterizedType.getActualTypeArguments()[0].getClass();
                        // 得到list集合的JSON数组
                        JSONArray jsonArr = jsonObject.getJSONArray(field.getName());

                        // 将fileObj转换为list
                        List<Object> listObj = (List<Object>) fieldObj;

                        // 为集合的每个元素实例赋值
                        for (int i = 0; i < jsonArr.length(); i++) {
                            // 遍历jsonArr 递归调用JSON2Entity 再将转换后的对象加入到list
                            listObj.add(JSON2Entity(jsonArr.getJSONObject(i), clz));
                        }
                        // 为obj属性赋值 只是此时的属性类型为list
                        setProperty(field, o, listObj);
                    } else if (fieldObj instanceof String) {
                        // 字符串直接赋值
                        setProperty(field, o, jsonObject.opt(field.getName()));

                    } else if (fieldObj instanceof Date) {
                        long l = jsonObject.optLong(field.getName());

                        setProperty(field, o, new Date(l));

                    } else if (jsonObject.opt(field.getName()).getClass().equals(JSONArray.class)) {
                        // 主要应对的是字段类型为object但是存储的确是一个数组
                        // 获取到json数组 将数组转换为List 赋值给当前字段
                        // 将jsonArray转换为List集合
                        JSONArray obj = jsonObject.getJSONArray(field.getName());
                        List<Object> list = new ArrayList<>();
                        for (Object oj : obj) {
                            list.add(oj);
                        }
                        setProperty(field, o, list);
                    } else {
                        setProperty(field, o, JSON2Entity(jsonObject.getJSONObject(field.getName()), typeClazz));
                    }
                }

            }

        } catch (InstantiationException | IllegalAccessException e) {
            System.out.println("异常2：" + e);
        }


        return o;
    }


    /**
     * 生成字段set方法名
     *
     * @param field
     * @return String
     */
    private static String generateFileMethName(Field field) {
        return "set" + field.getName().substring(0, 1).toUpperCase(Locale.getDefault()) + field.getName().substring(1);
    }


    /**
     * 设置字段的值
     *
     * @param field 需要赋值的字段
     * @param o     实例对象
     * @param val   要赋的值
     */
    private static void setProperty(Field field, Object o, Object val) {
        Class<?> clazz = o.getClass();
        // 获取字段的set方法
        Method method = getOwnAndSuperDeclaredMethod(clazz, field);
        // 调用invoke方法 需要传入方法的实例对象
        method.setAccessible(true);
        try {
            method.invoke(o, val);
        } catch (IllegalAccessException | InvocationTargetException e) {
            System.out.println("异常3：" + e);
        }

    }


    /**
     * 将数组转换为json字符串
     *
     * @param arr
     * @return String
     */
    private static String array2JsonString(Object arr) {
        if (arr == null) return StringNull;
        StringBuilder sb = new StringBuilder();
        sb.append('[');
        int length = Array.getLength(arr);
        for (int i = 0; i < length; i++) {
            sb.append(entity2JSON(Array.get(arr, i)));

            // 例：length = 4 i = 3 此时已是最后一个元素 所以判断是否最后一个元素的条件为 length - i > 1
            if (length - i > 1) sb.append(',');
        }
        sb.append(']');
        return sb.toString();
    }


    /**
     * 先将对象转换为map,就可以调用map2JsonString直接转换
     *
     * @param entity
     * @return String
     */
    private static String entityObject2JsonString(Object entity) {
        if (entity == null) return StringNull;
        // 获取class对象
        Class<?> cls = entity.getClass();

        // 将对象转换为map, key-value格式, 就可以调用map2JsonString直接转换
        HashMap<Object, Object> map = new HashMap<>();

        // 拿到所有的属性

        List<Field> declaredFields = getOwnAndSuperDeclaredFields(cls);
        for (Field field : declaredFields) {
            // 比对当前字符修饰符类型是否为static
            // 静态属性直接跳过
            if (Modifier.isStatic(field.getModifiers())) continue;
            String name = field.getName();
            // 屏蔽语言检查 破坏访问限制
            field.setAccessible(true);
            // 设置变量取出value
            Object val = null;

            try {
                // 反射调用当前字段的get方法需要传入实例对象entity
                val = field.get(entity);
                // 如果为null 直接跳出循环
                if (val == null) continue;
            } catch (IllegalAccessException e) {
                // 若取不到 直接将value设置为null 不抛出异常
                map.put(name, null);
                continue;
            }
            map.put(name, val);
        }
        return map2JsonString(map);
    }

    /**
     * 将collection对象转换为json字符串
     *
     * @param collection
     * @return String
     */
    private static String collection2JsonString(Collection<Object> collection) {
        if (collection == null) return StringNull;
        StringBuilder sb = new StringBuilder();
        sb.append('[');
        Iterator<Object> iterator = collection.iterator();
        while (iterator.hasNext()) {
            sb.append(entity2JSON(iterator.next()));
            if (iterator.hasNext()) sb.append(',');
        }
        sb.append(']');
        return sb.toString();
    }


    /**
     * 将map对象转换为json字符串
     *
     * @param map
     * @return String
     */
    private static String map2JsonString(Map<Object, Object> map) {
        // 判空处理
        if (map == null) return StringNull;
        // 选用StringBuilder字符串拼接效率最高
        StringBuilder sb = new StringBuilder();
        // 集合对象转为json前后需要加上"{"
        sb.append("{");
        // 迭代器集合
        Iterator<Object> iterator = map.keySet().iterator();
        while (iterator.hasNext()) {
            Object next = iterator.next();
            // key为null抛弃当前键值对
            if (next == null) continue;
            // 不为null 将key转换为String
            String key = next.toString();
            // 添加key的双引号
            sb.append(entity2JSON(key));
            sb.append(':').append(entity2JSON(map.get(key)));
            // 在当前键值对添加分隔符逗号
            if (iterator.hasNext()) sb.append(',');
        }
        sb.append("}");
        return sb.toString();
    }


    /**
     * 避免字符转义
     *
     * @param s
     * @return String
     */
    private static String escape(String s) {
        if (s == null) return null;
        StringBuilder sb = new StringBuilder();
        escape(s, sb);
        return sb.toString();

    }

    private static void escape(String s, StringBuilder sb) {
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            switch (ch) {
                case '"':
                    sb.append("\\\"");
                    break;
                case '\\':
                    sb.append("\\\\");
                    break;
                case '\b':
                    sb.append("\\b");
                    break;
                case '\f':
                    sb.append("\\f");
                    break;
                case '\n':
                    sb.append("\\n");
                    break;
                case '\r':
                    sb.append("\\r");
                    break;
                case '\t':
                    sb.append("\\t");
                    break;
                case '/':
                    sb.append("\\/");
                    break;
                default:
                    if (ch <= '\u001F' || ch >= '\u007F' && ch <= '\u009F' || ch >= '\u2000' && ch <= '\u20FF') {
                        String ss = Integer.toHexString(ch);
                        sb.append("\\u");
                        for (int k = 0; k < 4 - ss.length(); k++) {
                            sb.append('0');
                        }
                        sb.append(ss.toUpperCase());
                    } else {
                        sb.append(ch);

                    }

            }

        }

    }


    /**
     * 获取当前类及其父类字段集合
     *
     * @param clz
     * @return List<Field>
     */
    private static List<Field> getOwnAndSuperDeclaredFields(Class clz) {
        List<Field> fields = new ArrayList<>();

        while (clz != null) {
            Field[] declaredFields = clz.getDeclaredFields();
            fields.addAll(Arrays.asList(declaredFields));
            // 递归获取父类 直到没有
            clz = clz.getSuperclass();
        }

        return fields;
    }


    /**
     * 查找子类及父类字段
     *
     * @param clz
     * @param field
     * @return
     */
    private static Method getOwnAndSuperDeclaredMethod(Class<?> clz, Field field) {
        Method method = null;
        while (clz != null) {
            try {
                method = clz.getDeclaredMethod(generateFileMethName(field), field.getType());
            } catch (NoSuchMethodException e) {
                clz = clz.getSuperclass();
            }
            if (method != null) return method;
        }
        return method;
    }
}